"""
序列化器
"""

import functools
import inspect

from devtools import debug

from my_typing import *

NON_FIELD_ERRORS = "__all__"


class BuiltinSignatureError(Exception):
    """
    内置函数签名不可检查
    引发此异常是为了让序列化程序可以引发有用的错误消息
    """


class ObjectDoesNotExist(Exception):
    """ The requested object does not exist """

    silent_variable_failure = True


class SkipField(Exception):
    pass


class DjangoValidationError(Exception):
    """ An error while validating data. """

    def __init__(
        self,
        message,
        code=None,
        params=None,
    ) -> None:
        """
        The `message` argument can be a single error, a list of errors, or a
        dictionary that maps field names to lists of errors. What we define as
        an "error" can be either a simple string or an instance of
        ValidationError with its message attribute set, and what we define as
        list or dictionary can be an actual `list` or `dict` or an instance
        of ValidationError with its `error_list` or `error_dict` attribute set.
        """

        super().__init__(message, code, params)

        if isinstance(message, DjangoValidationError):
            if hasattr(message, "error_dict"):
                message = message.error_dict

            elif not hasattr(message, "message"):
                message = message.error_list  # noqa

            else:
                message, code, params = message.message, message.code, message.params  # noqa

        if isinstance(message, dict):
            self.error_dict = {}

            for field, messages in message.items():
                if not isinstance(messages, DjangoValidationError):
                    messages = DjangoValidationError(messages)

                self.error_dict[field] = messages.error_list

        elif isinstance(message, list):
            self.error_list = []

            for message in message:
                # Normalize plain strings to instances of ValidationError.
                if not isinstance(message, DjangoValidationError):
                    message = DjangoValidationError(message)

                if hasattr(message, "error_dict"):
                    self.error_list.extend(sum(message.error_dict.values(), []))

                else:
                    self.error_list.extend(message.error_list)

        else:
            self.message = message
            self.code = code
            self.params = params
            self.error_list = [self]

    @property
    def message_dict(self):
        # 如果此 ValidationError 没有 error_dict，则触发 AttributeError
        getattr(self, "error_dict")

        return dict(self)

    @property
    def messages(self):
        if hasattr(self, "error_dict"):
            return sum(dict(self).values(), [])

        return list(self)

    def update_error_dict(self, error_dict):
        if hasattr(self, "error_dict"):
            for field, error_list in self.error_dict.items():
                error_dict.setdefault(field, []).extend(error_list)

        else:
            error_dict.setdefault(NON_FIELD_ERRORS, []).extend(self.error_list)

        return error_dict

    def __iter__(self):
        if hasattr(self, "error_dict"):
            for field, errors in self.error_dict.items():
                yield field, list(DjangoValidationError(errors))

        else:
            for error in self.error_list:
                message = error.message

                if error.params:
                    message %= error.params

                yield str(message)

    def __str__(self) -> str:
        if hasattr(self, "error_dict"):
            return repr(dict(self))

        return repr(list(self))

    def __repr__(self):
        return "ValidationError(%s)" % self


class ErrorDetail(str):
    """
    A string-like object that can additionally have a code.
    """

    code = None

    def __new__(cls, string, code=None) -> "ErrorDetail":
        self = super().__new__(cls, string)
        self.code = code
        self.string = string

        return self

    def __eq__(self, other) -> bool:
        r = super().__eq__(other)

        try:
            return (
                r
                and self.code == other.code
            )

        except AttributeError:
            return r

    def __ne__(self, other) -> bool:
        return not self.__eq__(other)

    def __repr__(self) -> str:
        return "ErrorDetail(string=%r, code=%r)" % (
            str(self),
            self.code,
        )

    def __hash__(self) -> int:
        return hash(str(self))


def is_simple_callable(obj) -> bool:
    """
    判断对象是否为简单的可调用对象

        - 如果对象是不带参数的可调用对象，则为 True

    Args:
        obj: 对象

    Returns:
        True / False
    """

    # 如果是内置函数，则抛出异常
    if inspect.isbuiltin(obj):
        raise BuiltinSignatureError(
            "Built-in function signatures are not inspectable. "  # noqa
            "Wrap the function call in a simple, pure Python function."
        )

    if not (
        inspect.isfunction(obj)
        or inspect.ismethod(obj)
        or isinstance(obj, functools.partial)
    ):
        return False

    sig = inspect.signature(obj)
    params = sig.parameters.values()

    return all(
        (
            param.kind == param.VAR_POSITIONAL
            or param.kind == param.VAR_KEYWORD
            or param.default != param.empty
        )
        for param in params
    )


def get_attribute(instance, attrs):
    """
    获取对象的属性

        - 与 Python 内置的 `getattr(instance, attr)` 类似，但采用嵌套属性列表，而不是单个属性
        - 还接受对象的属性查找或字典查找

    Args:
        instance: 对象
        attrs: 属性列表

    Returns:
        属性值
    """

    for attr in attrs:
        try:
            if isinstance(instance, Mapping):
                instance = instance[attr]

            else:
                instance = getattr(instance, attr)

        except ObjectDoesNotExist:
            return None

        if is_simple_callable(instance):
            try:
                instance = instance()

            except (AttributeError, KeyError) as exc:
                # 如果我们在这里引发 `Attribute` 或 `KeyError`，它将被视为 `Field.get_attribute()` 中的省略字段
                # 相反，我们引发 `ValueError` 以确保异常未被屏蔽
                raise ValueError(f'Exception raised in callable attribute "{attr}"; original exception was: {exc}')

    return instance


class ReturnList(list):
    """
    从 `serializer.data `返回 `SerializerList `类的对象
    包括到序列化器实例的反向链接，供渲染器在需要更丰富的字段信息时使用
    """

    def __init__(self, *args, **kwargs) -> None:
        self.serializer = kwargs.pop("serializer")
        super().__init__(*args, **kwargs)

    def __repr__(self) -> str:
        return list.__repr__(self)

    def __reduce__(self):
        # Pickle 这些对象将会删除 `.serializer` 反向链接，但保留原始数据
        return list, (list(self),)


class ReturnDict(OrderedDict):
    """
    从 `serializer.data` 返回 `Serializer` 类的对象
    包括到序列化器实例的反向链接，供渲染器在需要更丰富的字段信息时使用
    """

    def __init__(self, *args, **kwargs) -> None:
        self.serializer = kwargs.pop("serializer")
        super().__init__(*args, **kwargs)

    def copy(self) -> "ReturnDict":
        return ReturnDict(self, serializer=self.serializer)

    def __repr__(self) -> str:
        return dict.__repr__(self)

    def __reduce__(self):
        # Pickle 这些对象将会删除 `.serializer` 反向链接，但保留原始数据
        return dict, (dict(self),)


def _get_full_details(detail):
    if isinstance(detail, list):
        return [
            _get_full_details(item)
            for item in detail
        ]

    elif isinstance(detail, dict):
        return {
            key: _get_full_details(value)
            for key, value in detail.items()
        }

    return {
        "message": detail,
        "code": detail.code
    }


def _get_error_details(
    data: ReturnDict | ReturnList | dict | list | str,
    default_code=None,
) -> ReturnList | list | ReturnDict | dict | ErrorDetail:
    """
    Descend into a nested data structure, forcing any lazy translation strings or strings into `ErrorDetail`.

    下降到嵌套数据结构，强制任何惰性翻译字符串或字符串转换成 `ErrorDetail`
    """

    if isinstance(data, list):
        ret = [
            _get_error_details(item, default_code)
            for item in data
        ]

        if isinstance(data, ReturnList):
            return ReturnList(ret, serializer=data.serializer)

        return ret

    elif isinstance(data, dict):
        ret = {
            key: _get_error_details(value, default_code)
            for key, value in data.items()
        }

        if isinstance(data, ReturnDict):
            return ReturnDict(ret, serializer=data.serializer)

        return ret

    return ErrorDetail(str(data))


class APIException(Exception):
    """
    自定义异常的基类
    子类应该提供 `.status_code` 和 `.default_detail` 属性
    """

    default_detail = "服务器发生错误"

    def __init__(self, detail=None):
        if detail is None:
            detail = self.default_detail

        self.detail = _get_error_details(detail)

    def __str__(self) -> str:
        return str(self.detail)

    def get_full_details(self):
        """
        Return both the message & code parts of the error details.

        Eg. {"name": [{"message": "This field is required.", "code": "required"}]}

        {
            "name": [
                {
                    "message": ErrorDetail(string="分类名称不能为空", code=None),
                    "code": None,
                },
            ],
            "desc": [
                {
                    "message": ErrorDetail(string="确保该字段至少有3个字符.", code=None),
                    "code": None,
                },
            ],
            "gender": [
                {
                    "message": ErrorDetail(string="-1 不是一个有效的选择", code=None),
                    "code": None,
                },
            ],
            "hobby": {
                1: [
                    {
                        "message": ErrorDetail(string="爱好项目至少有2个字符", code=None),
                        "code": None,
                    },
                ],
            },
        }
        """

        return _get_full_details(self.detail)

    @property
    def simple_details(self) -> DictStrStr:
        """
        简约版的异常详情

        >>> {
        ...     "name": "分类名称不能为空",
        ...     "desc": "确保该字段至少有3个字符.",
        ...     "gender": "-1 不是一个有效的选择",
        ... }
        """

        full_details = self.get_full_details().copy()
        error_dict = {}
        debug(full_details)

        for field, details in full_details.items():
            details: ListDict | dict[int, ListDict]
            msg_list: ListStr = []

            if isinstance(details, list):
                for detail in details:
                    error_detail = detail.get("message")

                    if (
                        isinstance(error_detail, ErrorDetail)
                        and hasattr(error_detail, "string")
                    ):
                        msg_list.append(error_detail.string)

            elif isinstance(details, dict):
                for idx, detail_list in details.items():
                    position: int = idx + 1

                    for detail in detail_list:
                        error_detail = detail.get("message")

                        if (
                            isinstance(error_detail, ErrorDetail)
                            and hasattr(error_detail, "string")
                        ):
                            msg_list.append(f"第{position}个内容：{error_detail.string}")

            msg_list = msg_list or ["未知错误"]
            error_dict[field] = "、".join(msg_list)

        return error_dict


class ValidationError(APIException):
    """ 校验错误 """

    def __init__(
        self,
        detail,
        *args,
        **kwargs,
    ) -> None:
        """
        Args:
            detail: 错误详情
        """

        super().__init__(*args, **kwargs)

        if not isinstance(detail, (dict, list)):
            detail = [detail]

        self.detail = _get_error_details(detail)

    def __str__(self) -> str:
        return str(self.detail)
